package kzgX

import (
	"fmt"

	gthCrypto "github.com/ethereum/go-ethereum/crypto"
	"github.com/protolambda/go-kzg"
	"github.com/protolambda/go-kzg/bls"
)

type KZGSetting struct {
	*kzg.KZGSettings
}

func GenerateSetup(secret string, n uint64) ([]bls.G1Point, []bls.G2Point) {
	return kzg.GenerateTestingSetup(secret, n)
}

// NewKZGsetting 初始化设置
func NewKZGsetting(secretG1 []bls.G1Point, secretG2 []bls.G2Point, rootscale uint8) *KZGSetting {
	fft := kzg.NewFFTSettings(rootscale)
	return &KZGSetting{
		kzg.NewKZGSettings(fft, secretG1, secretG2),
	}
}

// Commit 对多项式 poly 进行承诺
func (s *KZGSetting) Commit(poly []bls.Fr) *bls.G1Point {
	return s.CommitToPoly(poly)
}

// ComputeProofSingle
func (s *KZGSetting) ComputeProofSingle(poly []bls.Fr, x bls.Fr, y bls.Fr) *bls.G1Point {
	// divisor = [-x, 1]
	divisor := [2]bls.Fr{}
	bls.SubModFr(&divisor[0], &bls.ZERO, &x)
	bls.CopyFr(&divisor[1], &bls.ONE)
	//for i := 0; i < 2; i++ {
	//	fmt.Printf("div poly %d: %s\n", i, FrStr(&divisor[i]))
	//}
	// quot = poly / divisor

	bls.SubModFr(&poly[0], &poly[0], &y)
	quotientPolynomial := polyLongDiv(poly, divisor[:])
	//for i := 0; i < len(quotientPolynomial); i++ {
	//	fmt.Printf("quot poly %d: %s\n", i, FrStr(&quotientPolynomial[i]))
	//}
	// evaluate quotient poly at shared secret, in G1
	return bls.LinCombG1(s.SecretG1[:len(quotientPolynomial)], quotientPolynomial)
}

// CheckProofSingle 验证单个值的证明
func (s *KZGSetting) CheckProofSingle(commit, proof *bls.G1Point, x, y *bls.Fr) bool {
	return s.KZGSettings.CheckProofSingle(commit, proof, x, y)
}

func (s *KZGSetting) ComputeProofMulti(poly []bls.Fr, xs, ys []bls.Fr) (out *bls.G1Point, err error) {
	if len(xs) != len(ys) {
		return nil, fmt.Errorf("len(xs) != len(ys)")
	}
	if len(poly) < len(xs)+1 {
		return nil, fmt.Errorf("polynomial poly(x) can not be of degree"+
			" equal or smaller than the number of given points+1."+
			" Polynomial poly(x) degree: %d, number of points: %d",
			len(poly), len(xs))
	}

	// z(x) = (x-z0)(x-z1)...(x-zn)
	z := zeroPolynomial(xs)
	if compareFrArray(z, []bls.Fr{}) {

	}

	// I(x) = Lagrange interpolation through (z0, y0), (z1, y1), ...
	interpolation, err := LagrangeInterpolation(xs, ys)
	if err != nil {
		return nil, err
	}

	// q(x) = ( p(x) - I(x) ) / z(x)
	pMinusI := polynomialSub(poly, interpolation)
	q, rem := polynomialDiv(pMinusI, z)
	for i, one := range rem {
		fmt.Printf("rem [%d] =  %s \n", i, bls.FrStr(&one))
	}
	if !compareFrArray(rem, arrayOfZeroes(len(rem))) {
		return nil,
			fmt.Errorf("remainder should be 0, instead is %d", rem)
	}

	// proof: e = [q(t)]₁
	e := evaluateG1(s.SecretG1[:len(q)], q)
	return e, nil
}

func (s *KZGSetting) CheckProofMulti(commitment *bls.G1Point, proof *bls.G1Point, xs []bls.Fr, ys []bls.Fr) (res bool) {
	// [z(s)]₂
	z := zeroPolynomial(xs)
	zG2 := evaluateG2(s.SecretG2[:len(z)], z) // [z(t)]₂ = z(t) G ∈ 𝔾₂

	// I(x) = Lagrange interpolation through (z0, y0), (z1, y1), ...
	interpolation, err := LagrangeInterpolation(xs, ys)
	if err != nil {
		return false
	}
	// [i(t)]₁
	iG1 := evaluateG1(s.SecretG1[:len(interpolation)], interpolation) // [i(t)]₁ = i(t) G ∈ 𝔾₁

	// c - [i(t)]₁
	var CommitmentMinuInterpolation bls.G1Point
	bls.SubG1(&CommitmentMinuInterpolation, commitment, iG1)

	return bls.PairingsVerify(&CommitmentMinuInterpolation, &s.SecretG2[0], proof, zG2)
}

// PolyComputeAt 计算 f(x) = y
func PolyComputeAt(x bls.Fr, f []bls.Fr) bls.Fr {
	var y bls.Fr
	bls.EvalPolyAt(&y, f, &x)
	return y
}

// DataToPoly 利用插值法将需要承诺的数据变成多项式系数
func DataToPoly(data [][]byte) []bls.Fr {
	root := generalRoot(data)
	poly := zeroPolynomial(root)
	return poly
}

func generalRoot(ins [][]byte) []bls.Fr {
	root := make([]bls.Fr, len(ins), len(ins))
	for i, one := range ins {
		hash := gthCrypto.Keccak256(one)
		FrFromByte32(&root[i], hash)
	}
	return root
}

// ByteToFr 将byte 转化成 Fr类型
func ByteToFr(in []byte) bls.Fr {
	var out bls.Fr
	hash := gthCrypto.Keccak256(in)
	FrFromByte32(&out, hash)
	return out
}

// zeroPolynomial returns the zero polynomial:
// z(x) = (x - z_0) (x - z_1) ... (x - z_{k-1})
func zeroPolynomial(zs []bls.Fr) []bls.Fr {
	var first bls.Fr
	bls.SubModFr(&first, &bls.ZERO, &zs[0])
	z := []bls.Fr{first, bls.ONE} // (x - z0)

	for i := 1; i < len(zs); i++ {
		var tmp bls.Fr
		bls.SubModFr(&tmp, &bls.ZERO, &zs[i])
		z = polynomialMul(z, []bls.Fr{tmp, bls.ONE}) // (x - zi)
	}
	return z
}
